/*
 *
 * Copyright (c) 2018-2022, Xilinx, Inc.
 * Copyright (c) 2022, Advanced Micro Devices, Inc.
 * All rights reserved.
 *
 * Author: Chris Lavin, Xilinx Research Labs.
 *
 * This file is part of RapidWright.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */
/**
 *
 */
package com.xilinx.rapidwright.design;

import java.io.IOException;
import java.io.OutputStream;
import java.util.Arrays;

import com.xilinx.rapidwright.device.Device;
import com.xilinx.rapidwright.edif.EDIFCell;
import com.xilinx.rapidwright.edif.EDIFPort;

/**
 * Generates Verilog or VHDL stub files to instantiate
 * design checkpoint files.
 *
 * Created on: Feb 7, 2018
 */
public class RTLStubGenerator {

    public static final String VERILOG_COMMENT = "//";
    public static final String VHDL_COMMENT = "--";

    public static EDIFPort[] getSortedPortList(EDIFCell c) {
        String[] portNames = new String[c.getPorts().size()];
        int idx = 0;
        for (EDIFPort p : c.getPorts()) {
            portNames[idx++] = p.getBusName();
        }
        Arrays.sort(portNames);
        EDIFPort[] ports = new EDIFPort[portNames.length];
        for (int i=0; i < portNames.length; i++) {
            ports[i] = c.getPort(portNames[i]);
        }
        return ports;
    }

    private static String getHeaderCommentString() {
        return "This file was generated by RapidWright " + Device.RAPIDWRIGHT_VERSION + ".";
    }

    public static void createVerilogStub(Design d, OutputStream out) {
        EDIFPort[] ports = getSortedPortList(d.getNetlist().getTopCell());
        try {
            out.write((VERILOG_COMMENT + " " + getHeaderCommentString() + "\n").getBytes());
            out.write("\n".getBytes());
            out.write((VERILOG_COMMENT + " This empty module with port declaration file causes synthesis tools to infer a black box for IP.\n").getBytes());
            out.write((VERILOG_COMMENT + " Please paste the declaration into a Verilog source file or add the file as an additional source.\n").getBytes());
            out.write(("module " + d.getName() +"(").getBytes());
            for (int i=0; i < ports.length; i++) {
                if (i>0) out.write(", ".getBytes());
                out.write(ports[i].getBusName().getBytes());
            }
            out.write(");\n".getBytes());
            for (int i=0; i < ports.length; i++) {
                EDIFPort p = ports[i];
                String dir = p.getDirection().name().toLowerCase();
                String range = p.isBus() ? "[" + p.getLeft() + ":" + p.getRight() + "]" : "";
                out.write(("  " + dir + " " + range + p.getBusName() + ";\n").getBytes());
            }
            out.write("endmodule\n".getBytes());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void createVHDLStub(Design d, OutputStream out) {
        EDIFPort[] ports = getSortedPortList(d.getNetlist().getTopCell());
        String topCellName = d.getNetlist().getTopCell().getName();
        try {
            out.write((VHDL_COMMENT + " " + getHeaderCommentString() + "\n").getBytes());
            out.write("library IEEE;\n".getBytes());
            out.write("use IEEE.STD_LOGIC_1164.ALL;\n\n".getBytes());
            out.write(("entity " + topCellName + " is\n").getBytes());
            out.write("  Port (\n".getBytes());
            for (int i=0; i < ports.length; i++) {
                EDIFPort p = ports[i];
                String dir = p.getDirection().name().toLowerCase().replace("put", "");
                String type = " STD_LOGIC";
                if (p.isBus()) {
                    String endian = p.getLeft() > p.getRight() ? " downto " : " to ";
                    type = " STD_LOGIC_VECTOR ( " + p.getLeft() + endian + p.getRight() + " )";
                }
                String semi = i == ports.length-1 ? "\n" : ";\n";
                out.write(("    " + p.getBusName() + " : " + dir  + type + semi).getBytes());
            }
            out.write("  );\n\n".getBytes());
            out.write(("end "+ topCellName+";\n\n").getBytes());
            out.write(("architecture stub of "+topCellName+" is\n").getBytes());
            out.write("attribute syn_black_box : boolean;\n".getBytes());
            out.write("attribute black_box_pad_pin : string;\n".getBytes());
            out.write("attribute syn_black_box of stub : architecture is true;\n".getBytes());
            out.write("begin\n".getBytes());
            out.write("end;\n".getBytes());
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}
